﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using XmlDatabase.Core.Properties;

namespace XmlDatabase.Core
{
    internal class XTaskManager
    {
        private XTaskManager() { }
        private XDatabase internaldb;
        private List<XChangeItem> tasks = new List<XChangeItem>();
        private Dictionary<String, FileStream> fs = new Dictionary<String, FileStream>();
        private Dictionary<String, XDocument> docs = new Dictionary<String, XDocument>();

        public XTaskManager(XDatabase db)
        {
            this.internaldb = db;
        }
        public void AddTask(XChangeItem task)
        {
            this.tasks.Add(task);
        }
        public XSubmitStatus Execute(Boolean continueOnError, Boolean hasTransaction)
        {
            StringBuilder stringBuilder = new StringBuilder();
            Boolean error = false;
            this.fs = new Dictionary<String, FileStream>();
            this.docs = new Dictionary<String, XDocument>();
            foreach (XChangeItem task in this.tasks)
            {
                try
                {
                    switch (task.Action)
                    {
                        case XChangeAction.AddOrUpdate:
                            this.AddOrUpdateObject(task.UserData);
                            continue;
                        case XChangeAction.Delete:
                            this.DeleteObject(task.UserData);
                            continue;
                        default:
                            continue;
                    }
                }
                catch (Exception ex)
                {
                    error = true;
                    stringBuilder.AppendFormat(Resources.ChangeActionError, task.UserData.ToString(), task.Action, ex.Message);
                    stringBuilder.AppendLine();
                    if (!continueOnError)
                        break;
                }
            }
            foreach (KeyValuePair<String, FileStream> f in this.fs)
            {
                f.Value.Close();
            }
            if (!error || !hasTransaction)
            {
                foreach (KeyValuePair<String, XDocument> doc in this.docs)
                {
                    doc.Value.Save(doc.Key);
                }
            }
            this.fs.Clear();
            this.docs.Clear();
            this.tasks.Clear();
            this.internaldb.Log.Flush();
            return new XSubmitStatus(error, stringBuilder.ToString());
        }
        private void AddOrUpdateObject(Object o)
        {
            String fullName = o.GetType().FullName;
            String name = o.GetType().Name;
            String index = String.Format(@"{0}\{1}\{2}\{3}\{4}", this.internaldb.FullName, "Entities", fullName, "Data", fullName);
            if (File.Exists(index))
            {
                if (!this.fs.Keys.Contains<String>(index))
                {
                    FileStream fileStream = new FileStream(index, FileMode.Open, FileAccess.ReadWrite, FileShare.None);
                    this.fs.Add(index, fileStream);
                    this.docs.Add(index, XDocument.Load(new StreamReader(fileStream)));
                }
            }
            else
            {
                XDocument xdocument = new XDocument(new Object[1]
                {
                  new XElement("XMLDatabase - Entities")
                });
                xdocument.Save(index);
                this.docs.Add(index, xdocument);
                this.fs.Add(index, new FileStream(index, FileMode.Open, FileAccess.ReadWrite, FileShare.None));
            }
            XDocument doc = this.docs[index];
            Guid id = this.internaldb.ids.GetObjectId(o);
            if (id != Guid.Empty)
            {
                doc.Root.Elements(name).Where<XElement>(
                   ((e) => e.Attribute("_uuid").Value == id.ToString()))
                    .FirstOrDefault<XElement>()
                    .ReplaceWith(o.ConvertToXml(id));
                this.internaldb.Log.WriteEx(String.Format(Resources.UpdateObject, id, o.ToString()));
            }
            else
            {
                id = Guid.NewGuid();
                XElement xml = o.ConvertToXml(id);
                doc.Root.Add(xml);
                this.internaldb.Log.WriteEx(String.Format(Resources.InsertObject, id, o.ToString()));
            }
        }
        private void DeleteObject(Object o)
        {
            Guid id = this.internaldb.ids.GetObjectId(o);
            String fullName = o.GetType().FullName;
            String name = o.GetType().Name;
            String index = String.Format("{4}\\{0}\\{1}\\{2}\\{3}.xml", "Entities", fullName, "Data", fullName, this.internaldb.FullName);
            if (id == Guid.Empty || !File.Exists(index))
            { 
                throw new XDatabaseException("DeleteObjectError", new String[1] { o.ToString() });
            }
            if (!this.fs.Keys.Contains<String>(index))
            {
                FileStream fileStream = new FileStream(index, FileMode.Open, FileAccess.ReadWrite, FileShare.None);
                this.fs.Add(index, fileStream);
                this.docs.Add(index, XDocument.Load(new StreamReader(fileStream)));
            }
            XElement xelement = this.docs[index].Root.Elements().Where<XElement>((e => e.Attribute("_uuid").Value == id.ToString())).FirstOrDefault<XElement>();
            if (xelement != null)
            {
                xelement.Remove();
            }
            this.internaldb.ids.Remove(o);
            this.internaldb.Log.WriteEx(String.Format(Resources.DeleteObject, id, o));
        }
    }
}
